// import com.alibaba.druid.sql.SQLUtils;
// import com.alibaba.druid.sql.ast.SQLExpr_ptr;
// import com.alibaba.druid.sql.ast.SQLName;
// import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr;
// import com.alibaba.druid.sql.ast.expr.SQLAllColumnExpr;
// import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
// import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
// import com.alibaba.druid.sql.ast.statement.*;
// import com.alibaba.druid.util.FnvHash;
// import com.alibaba.druid.util.StringUtils;

// import java.util.*;
// import java.util.concurrent.ConcurrentHashMap;
#include "Schema.h"
#include "../../utils/instanceof.h"
#include "../SQLUtils.h"
#include "SchemaRepository.h"
#include "../../utils/FnvHash.h"
#include "../../utils/StringUtils.h"
#include "SchemaObjectType.h"
#include "SchemaObject.h"
#include "../ast/expr/SQLAllColumnExpr.h"
#include "../ast/expr/SQLAggregateExpr.h"
#include "../ast/statement/SQLSelectItem.h"
#include "../ast/statement/SQLJoinTableSource.h"

Schema::Schema(SchemaRepository_ptr repository)
    : Schema(repository, nullptr)
{
}

Schema::Schema(SchemaRepository_ptr repository, string_ptr name)
{
  this->repository = repository;
  this->setName(name);
}

Schema::Schema(SchemaRepository_ptr repository, string_ptr catalog, string_ptr name)
{
  this->repository = repository;
  this->catalog = catalog;
  this->name = name;
}

SchemaRepository_ptr Schema::getRepository()
{
  return repository;
}

string_ptr Schema::getName()
{
  return name;
}

string_ptr Schema::getSimpleName()
{
  if (name != nullptr)
  {
    int p = name->find('.');
    if (p != -1)
    {
      SQLExpr_ptr expr = SQLUtils::toSQLExpr(name, repository->dbType);
      if (instanceof <SQLExpr, SQLPropertyExpr>(expr))
      {
        return (std::dynamic_pointer_cast<SQLPropertyExpr>(expr))->getSimpleName();
      }
      else
      {
        return make_string_ptr(name->substr(p + 1));
      }
    }
    else
    {
      return name;
    }
  }
  return nullptr;
}

void Schema::setName(string_ptr name)
{
  this->name = name;

  if (name != nullptr && name->find('.') != -1)
  {
    SQLExpr_ptr expr = SQLUtils::toSQLExpr(name, repository->dbType);
    if (instanceof <SQLExpr, SQLPropertyExpr>(expr))
    {
      catalog = (std::dynamic_pointer_cast<SQLPropertyExpr>(expr))->getOwnernName();
    }
  }
}

SchemaObject_ptr Schema::findTable(string_ptr tableName)
{
  long hashCode64 = FnvHash::hashCode64(tableName);
  return findTable(hashCode64);
}

SchemaObject_ptr Schema::findTable(long nameHashCode64)
{
  auto tmp = objects.find(nameHashCode64);
  SchemaObject_ptr object = nullptr;
  if (tmp != objects.end())
  {
    object = tmp->second;
  }

  if (object != nullptr && object->getType()->name_ == SchemaObjectType::Table->name_)
  {
    return object;
  }

  return nullptr;
}

SchemaObject_ptr Schema::findView(string_ptr viewName)
{
  long hashCode64 = FnvHash::hashCode64(viewName);
  return findView(hashCode64);
}

SchemaObject_ptr Schema::findView(long nameHashCode64)
{
  auto tmp = objects.find(nameHashCode64);
  SchemaObject_ptr object = nullptr;
  if (tmp != objects.end())
  {
    object = tmp->second;
  }

  if (object != nullptr && object->getType()->name_ == SchemaObjectType::View->name_)
  {
    return object;
  }

  return nullptr;
}

SchemaObject_ptr Schema::findTableOrView(string_ptr tableName)
{
  long hashCode64 = FnvHash::hashCode64(tableName);
  return findTableOrView(hashCode64);
}

SchemaObject_ptr Schema::findTableOrView(long hashCode64)
{
  auto tmp = objects.find(hashCode64);
  SchemaObject_ptr object = nullptr;
  if (tmp != objects.end())
  {
    object = tmp->second;
  }

  if (object == nullptr)
  {
    return nullptr;
  }

  SchemaObjectType_ptr type = object->getType();
  if (type->name_ == SchemaObjectType::Table->name_ || type->name_ == SchemaObjectType::View->name_)
  {
    return object;
  }

  return nullptr;
}

SchemaObject_ptr Schema::findFunction(string_ptr functionName)
{
  functionName = SQLUtils::normalize(functionName);
  string_ptr lowerName = StringUtils::toLower(functionName);
  auto tmp = functions.find(atol(lowerName->c_str()));
  if (tmp != functions.end())
  {
    return tmp->second;
  }
  return nullptr;
}

BOOL_ptr Schema::isSequence(string_ptr name)
{
  long nameHashCode64 = FnvHash::hashCode64(name);
  auto tmp = objects.find(nameHashCode64);
  SchemaObject_ptr object = nullptr;
  if (tmp != objects.end())
  {
    object = tmp->second;
  }
  return (object != nullptr && object->getType()->name_ == SchemaObjectType::Sequence->name_)?BOOL::TRUE:BOOL::FALSE;
}

SchemaObject_ptr Schema::findTable(SQLTableSource_ptr tableSource, string_ptr alias)
{
  if (instanceof <SQLTableSource, SQLExprTableSource>(tableSource))
  {
    if (StringUtils::equalsIgnoreCase(alias, tableSource->computeAlias()))
    {
      SQLExprTableSource_ptr exprTableSource = std::dynamic_pointer_cast<SQLExprTableSource>(tableSource);

      SchemaObject_ptr tableObject = exprTableSource->getSchemaObject();
      if (tableObject != nullptr)
      {
        return tableObject;
      }

      SQLExpr_ptr expr = exprTableSource->getExpr();
      if (instanceof <SQLExpr, SQLIdentifierExpr>(expr))
      {
        long tableNameHashCode64 = (std::dynamic_pointer_cast<SQLIdentifierExpr>(expr))->nameHashCode64();

        tableObject = findTable(tableNameHashCode64);
        if (tableObject != nullptr)
        {
          exprTableSource->setSchemaObject(tableObject);
        }
        return tableObject;
      }

      if (instanceof <SQLExpr, SQLPropertyExpr>(expr))
      {
        long tableNameHashCode64 = (std::dynamic_pointer_cast<SQLPropertyExpr>(expr))->nameHashCode64();

        tableObject = findTable(tableNameHashCode64);
        if (tableObject != nullptr)
        {
          exprTableSource->setSchemaObject(tableObject);
        }
        return tableObject;
      }
    }
    return nullptr;
  }

  if (instanceof <SQLTableSource, SQLJoinTableSource>(tableSource))
  {
    SQLJoinTableSource_ptr join = std::dynamic_pointer_cast<SQLJoinTableSource>(tableSource);
    SQLTableSource_ptr left = join->getLeft();

    SchemaObject_ptr tableObject = findTable(left, alias);
    if (tableObject != nullptr)
    {
      return tableObject;
    }

    SQLTableSource_ptr right = join->getRight();
    tableObject = findTable(right, alias);
    return tableObject;
  }

  return nullptr;
}

SQLColumnDefinition_ptr Schema::findColumn(SQLTableSource_ptr tableSource, SQLSelectItem_ptr selectItem)
{
  if (selectItem == nullptr)
  {
    return nullptr;
  }

  return findColumn(tableSource, selectItem->getExpr());
}

SQLColumnDefinition_ptr Schema::findColumn(SQLTableSource_ptr tableSource, SQLExpr_ptr expr)
{
  SchemaObject_ptr object = findTable(tableSource, expr);
  if (object != nullptr)
  {
    if (instanceof <SQLExpr, SQLAggregateExpr>(expr))
    {
      SQLAggregateExpr_ptr aggregateExpr = std::dynamic_pointer_cast<SQLAggregateExpr>(expr);
      string_ptr function = aggregateExpr->getMethodName();
      if (StringUtils::equalsIgnoreCase(make_string_ptr("min"), function) ||
          StringUtils::equalsIgnoreCase(make_string_ptr("max"), function))
      {
        SQLExpr_ptr arg = *aggregateExpr->getArguments()->begin();
        expr = arg;
      }
    }

    if (instanceof <SQLExpr, SQLName>(expr))
    {
      return object->findColumn((std::dynamic_pointer_cast<SQLName>(expr))->getSimpleName());
    }
  }

  return nullptr;
}

SchemaObject_ptr Schema::findTable(SQLTableSource_ptr tableSource, SQLSelectItem_ptr selectItem)
{
  if (selectItem == nullptr)
  {
    return nullptr;
  }

  return findTable(tableSource, selectItem->getExpr());
}

SchemaObject_ptr Schema::findTable(SQLTableSource_ptr tableSource, SQLExpr_ptr expr)
{
  if (instanceof <SQLExpr, SQLAggregateExpr>(expr))
  {
    SQLAggregateExpr_ptr aggregateExpr = std::dynamic_pointer_cast<SQLAggregateExpr>(expr);
    string_ptr function = aggregateExpr->getMethodName();
    if (StringUtils::equalsIgnoreCase(make_string_ptr("min"), function) ||
        StringUtils::equalsIgnoreCase(make_string_ptr("max"), function))
    {
      SQLExpr_ptr arg = *aggregateExpr->getArguments()->begin();
      return findTable(tableSource, arg);
    }
  }

  if (instanceof <SQLExpr, SQLPropertyExpr>(expr))
  {
    string_ptr ownerName = (std::dynamic_pointer_cast<SQLPropertyExpr>(expr))->getOwnernName();
    return findTable(tableSource, ownerName);
  }

  if (instanceof <SQLExpr, SQLAllColumnExpr>(expr) ||
      instanceof <SQLExpr, SQLIdentifierExpr>(expr))
  {
    if (instanceof <SQLTableSource, SQLExprTableSource>(tableSource))
    {
      return findTable(tableSource, tableSource->computeAlias());
    }

    if (instanceof <SQLTableSource, SQLJoinTableSource>(tableSource))
    {
      SQLJoinTableSource_ptr join = std::dynamic_pointer_cast<SQLJoinTableSource>(tableSource);

      SchemaObject_ptr table = findTable(join->getLeft(), expr);
      if (table == nullptr)
      {
        table = findTable(join->getRight(), expr);
      }
      return table;
    }
    return nullptr;
  }

  return nullptr;
}

std::unordered_map<std::string, SchemaObject_ptr> Schema::getTables(SQLTableSource_ptr x)
{
  std::unordered_map<std::string, SchemaObject_ptr> tables;
  computeTables(x, tables);
  return tables;
}

void Schema::computeTables(SQLTableSource_ptr x, std::unordered_map<std::string, SchemaObject_ptr> tables)
{
  if (x == nullptr)
  {
    return;
  }

  if (instanceof <SQLTableSource, SQLExprTableSource>(x))
  {
    SQLExprTableSource_ptr exprTableSource = std::dynamic_pointer_cast<SQLExprTableSource>(x);

    SQLExpr_ptr expr = exprTableSource->getExpr();
    if (instanceof <SQLExpr, SQLIdentifierExpr>(expr))
    {
      long tableNameHashCode64 = (std::dynamic_pointer_cast<SQLIdentifierExpr>(expr))->nameHashCode64();
      string_ptr tableName = (std::dynamic_pointer_cast<SQLIdentifierExpr>(expr))->getName();

      SchemaObject_ptr table = exprTableSource->getSchemaObject();
      if (table == nullptr)
      {
        table = findTable(tableNameHashCode64);

        if (table != nullptr)
        {
          exprTableSource->setSchemaObject(table);
        }
      }

      if (table != nullptr)
      {
        tables.insert(std::make_pair(tableName->c_str(), table));

        string_ptr alias = x->getAlias();
        if (alias != nullptr && !StringUtils::equalsIgnoreCase(alias, tableName))
        {
          tables.insert(std::make_pair(alias->c_str(), table));
        }
      }
    }

    return;
  }

  if (instanceof <SQLTableSource, SQLJoinTableSource>(x))
  {
    SQLJoinTableSource_ptr join = std::dynamic_pointer_cast<SQLJoinTableSource>(x);
    computeTables(join->getLeft(), tables);
    computeTables(join->getRight(), tables);
  }
}

int Schema::getTableCount()
{
  int count = 0;
  for (auto tmp : this->objects)
  {
    SchemaObject_ptr object = tmp.second;
    if (object->getType()->name_ == SchemaObjectType::Table->name_)
    {
      count++;
    }
  }
  return count;
}

SchemaObject_list_ptr Schema::getObjects()
{
  SchemaObject_list_ptr out = std::make_shared<std::list<SchemaObject_ptr>>();
  for (auto tmp : this->objects)
  {
    out->push_back(tmp.second);
  }

  return out;
}

BOOL_ptr Schema::removeObject(long nameHashCode64)
{
  return ((this->objects.erase(nameHashCode64)))?BOOL::TRUE:BOOL::FALSE; //!= (this->objects.end());
}

int Schema::getViewCount()
{
  int count = 0;
  // for (SchemaObject object : this->objects.values())
  for (auto tmp : this->objects)
  {
    SchemaObject_ptr object = tmp.second;
    if (object->getType()->name_ == SchemaObjectType::View->name_)
    {
      count++;
    }
  }
  return count;
}

string_list_ptr Schema::showTables()
{
  string_list_ptr tables = std::make_shared<std::list<string_ptr>>();
  // for (SchemaObject object : objects.values())
  for (auto tmp : this->objects)
  {
    SchemaObject_ptr object = tmp.second;
    if (object->getType()->name_ == SchemaObjectType::Table->name_)
    {
      tables->push_back(object->getName());
    }
  }
  // Collections.sort(tables);
  return tables;
}

string_ptr Schema::getCatalog()
{
  return catalog;
}
